if (! this.EventBroadcaster) load("EventBroadcaster.asc");
if (! this.RTCFComponent) load("RTCFComponent.asc");
if (! this.User) load("User.asc");
_executionContext = this;

try {var dummy = RTCF} catch(e){

/**
 *
 */
function RTCF(){

	// Component object keeps track of component instances by id.
	this.componentManager = new ComponentManager();
	
	// Pending component array is only created when needed to store components
	// that ask the framework to keep clients in a pending state while the 
	// component(s) initialize.
	// See addPendingComponent and removePendingComponent.
	this.pendingComponents = null;
	
	// Pending Client object is only created when needed. See addPendingClient and removePendingClient.
	// Pending client in this case means a client waiting while components initialize.
	this.pendingClients = null;
	
	// authenticationClients is used to keep track of clients that are pending authentication.
	// See autnethenticationResult and authenticationFault
	this.authenticationClients = null;
	
	// authorizationClients is used to keep track of clients that are pending authorization.
	// See authorizationResult and authorizationFault
	this.authorizationClients = null;
		
	// authorizedClients is used to keep track of clients that have been allowed to connect.
	// NOT INTEGRATED INTO CODE YET...
	this.authorizationClients = new PendingClients(this); // NEED TO CHANGE NAME OF PendingClients class to ?? ClientCollection??
	
	this.allowNonCredentialedConnections = false;
		
		
	// Hijack the application object in a similar way to what the original Macromedia Component framework did.
	// Makes the real application into an event broadcaster.
	this.hijackApplication();
	
	var rtcf = this;
	// Setup clients to do method routing to components:
	Client.prototype.c2s = function(componentID, methodName){
		
		var component = rtcf.componentManager.getByID(componentID);
		if (! component){
			throw("ERROR! c2s: can't get component with id: " + componentID);
			return;
		}
		
		var method = component[methodName];
		
		if (! method){
			throw("ERROR! c2s: Component " + componentID + " doesn't have a remote method named: " + methodName);
			return;
		}
		
		if (! method.roles){
			throw("ERROR! c2s: Component " + componentID + "." + methodName + " has not been allowed as a remote method.");
			return;
		}
		
		// A non-zero length roles array must match a user's role:
		if (method.roles.length > 0){
			throw("ERROR! c2s: User/Method Role Matching has not been enabled in this release!");
			return;		
		}
		
		var args = [this];
		for (var i = 2; i < arguments.length; i++){
			args.push(arguments[i]);
		}
		return method.apply(component, args);
	}
}

RTCF.EVENT_HANDLER_NAMES = ["onConnect", "onDisconnect", "onAuthenticate", "onAuthorize", "onAuthorizedConnect", "onAuthorizedDisconnect", "onRejectedDisconnect", "onAppStart", "onAppStop", "onStatus", "onPublish", "onUnpublish"];


var proto = RTCF.prototype;

//// ==== ---- Component Management Methods ---- ==== ////

/**
 *
 *
 */
proto.createComponent = function(id, classPath, resourcePath){

	// Maks sure there is an id:
	if (! id){
		trace("ERROR! rtcf.createComponent: component must have an id: " + id);
		return null;
	}
	
	// Maks sure there a class path:
	if (! classPath || typeof classPath != "string"){
		trace("ERROR! rtcf.createComponent: component must have a classPath: " + classPath);
		return null;
	}	
	
	// Maks sure there a resourcePath:
	if (! resourcePath || typeof resourcePath != "string"){
		trace("ERROR! rtcf.createComponent: component must have a resourcePath: " + resourcePath);
		return null;
	}
	
	// Append a / to the resource path if there isn't one already:
	if(resourcePath.charAt(resourcePath.length - 1) != "/") resourcePath += "/";
	
	// Make sure the id is unique
	var component = this.componentManager.getByID(id);
	
	if (component){
		trace("ERROR! rtcf.createComponent: component with id: " + id + " already exists.");
		return null;
	}
	
	// Make sure the resource path is not shared
	component = this.componentManager.getByResourcePath(resourcePath);
	if (component){
		trace("ERROR! rtcf.createComponent: component with id: " + id + " is using the same resource path: " + resourcePath);
		return null;
	}
	
	var componentClass = null;
	var className = "";
	
	// If necessary load the class based on the classPath
	if (! this.componentManager.classLoaded(classPath)){
		var filePath = "rtcf/components/" + classPath.replace(/\./g, "/") + ".asc";
		try{
			load(filePath);
		}
		catch(e){
			trace("ERROR! rtcf.createComponent: can't load a class file at: " + filePath);
			return null;
		}
	}

	// The class is loaded so get the class name and a reference to the class
	className = classPath.split(".").pop();
	componentClass = _executionContext[className];
	if (! componentClass){
		trace("ERROR! rtcf.createComponent: can't find a class class for: " + className);
		return null;
	}
	
	// Instantiate the component
	component = new componentClass();
	RTCFComponent.apply(component, [id, classPath, resourcePath]);
	
	
	if (!component){
		trace("ERROR! rtcf.createComponent: can't create component from " + classPath);
		return null;
	}
	
	// save the component instance under its id and resource path
	this.componentManager.addComponent(component, id, classPath, resourcePath);
	component.rtcf = this;
	component.init();
	
	// now return it
	
	return component;
}

/**
 * Components can indicate that no clients should be authenticated 
 * until they are ready by calling this method.
 */
proto.addPendingComponent = function(component){
	var hashID = EventBroadcaster.getHashID(component);
	if (hashID){
		if (!this.pendingComponents) {
			this.pendingComponents = {length: 0};
		}
		this.pendingComponents[hashID] = component;
		this.pendingComponents.length++;
		if (this.pendingComponents.length == 1){
			// Hijack the onConnect method
			this.application.onConnect = this.application.onPendingConnect;
			this.application.onDisconnect = this.application.onPendingDisconnect;
		}
	}
}

/**
 * Components that called addPendingComponent can call
 * removePendingComponent to indicate they are ready.
 */
proto.removePendingComponent = function(component){
	//trace("removePendingComponent>");
	var hashID = EventBroadcaster.getHashID(component);
	if (hashID){
		if (!this.pendingComponents) {
			trace("ERROR: Attempt to remove a pending component that is not pending!");
			return;
		}
		if ( this.pendingComponents[hashID] == component){
			delete this.pendingComponents[hashID];
			this.pendingComponents.length--;
		}
		if (this.pendingComponents.length == 0){
			// Restore the onConnect method and feed it any pending clients.
			this.application.onConnect = this.application.onNormalConnect;
			this.application.onDisconnect = this.application.onNormalDisconnect;
			this.processPendingClients();
		}
	}
}

//// ==== ---- Client Management Methods ---- ==== ////

proto.addAuthenticatonAttempt = function(client, listenerCount){
	if (! this.authenticationClients) this.authenticationClients = new PendingClients(this);
	var authClientRecord = {
		client: client,
		id: client.id,
		listenerCount: listenerCount,
		listenerReports: 0,
		timestamp: new Date(),
		status: "pending"
	}
	this.authenticationClients.addClient(authClientRecord);
}

/**
 * If authenticationResult is called by any component it means the client
 * has been authenticated. If it is the last component to report we can
 * delete the authenticationClients object that keeps track of authentication
 * information related to this client.
 * More than one component may authenticate a client. If so we only use the
 * first authentication and call onAuthenticate only once.
 */
proto.authenticationResult = function(client, user){
	// If we get here a listener has authenticated a client.
	var authClientRecord = this.authenticationClients.getClientByID(client.id);
	
	if (! authClientRecord){
		trace("ERROR! authenticationResult: Can't get authClientRecord for client id: " + client.id + " Rejecting client connection.");
		this.application.rejectConnection(client);
		return;
	}
	
	authClientRecord.listenerReports++;
	
	// If its the last one remove it and authenticate it.
	if (authClientRecord.listenerReports >= authClientRecord.listenerCount){
		this.authenticationClients.removeClientByID(client.id);
	}
	
	// The client may be authenticated by more than one component so
	// don't call onAuthenticate again if it has already been called.
	if (authClientRecord.status != "authenticated"){
		this.application.onAuthenticate(client, user);
	}
	
	// Indicate that the client has been authenticated so it will not be rejected 
	// and so onAuthenticate will not be called a second time.
	authClientRecord.status = "authenticated";
}

/**
 * authenticationFault is called when a component cannot authenticate a client.
 * This method does nothing unless it is the last component listening for onConnect
 * calls to report and the status of the client is still pending.
 * (If the status is authenticate the client has been authenticated so nothing 
 *  should be done.)
 */
proto.authenticationFault = function(client, fault){

	// If we get here a listener could not authenticate a client.
	var authClientRecord = this.authenticationClients.getClientByID(client.id);
	
	// Make sure we have an authentication record for this client.
	if (! authClientRecord){
		trace("ERROR! authenticationFault: Can't get authClientRecord for client id: " + client.id + " Rejecting client connection.");
		trace("fault> " + fault);
		this.application.rejectConnection(client, {msg:"AuthenticationFault"});
		return;
	}
	
	authClientRecord.listenerReports++;
	
	// If this is the last listener to report and the status is still pending or authenticated reject the client.
	if (authClientRecord.listenerReports >= authClientRecord.listenerCount){
		this.authenticationClients.removeClientByID(client.id);
		if (authClientRecord.status == "pending"){
			this.application.onRejectedDisconnect(client, fault);
			this.application.rejectConnection(client, fault);
		}
	}
}


proto.addAuthorizationAttempt = function(client, listenerCount){
	if (! this.authorizationClients) this.authorizationClients = new PendingClients(this);
	var authClientRecord = {
		client: client,
		id: client.id,
		listenerCount: listenerCount,
		listenerReports: 0,
		timestamp: new Date(),
		status: "pending"
	}
	this.authorizationClients.addClient(authClientRecord);
}

/**
 * If any component rejects access then the client is rejected. So authorizationResult
 * only calls onAuthorize when it is the last component to report and the status is 
 * still pending. When onAuthorize is called the client is accepted and then 
 * onAuthorizedConnect is called.
 */
proto.authorizationResult = function(client, user){
	// If we get here a listener has authorized a client.
	var authClientRecord = this.authorizationClients.getClientByID(client.id);
	
	if (! authClientRecord){
		trace("ERROR! authorizationResult: Can't get authClientRecord for client id: " + client.id + " Rejecting client connection.");
		this.application.rejectConnection(client);
		return;
	}
	
	authClientRecord.listenerReports++;
	
	if (authClientRecord.listenerReports >= authClientRecord.listenerCount){
		this.authorizationClients.removeClientByID(client.id);
		if (authClientRecord.status == "pending"){
			if (! client.rtcf) client.rtcf = {user: user};
			this.application.onAuthorize(client, user);
			this.application.acceptConnection(client);
			this.setClientAccessPaths(client);
			this.configClient(client, user);
			this.application.onAuthorizedConnect(client, user);			
		}
	}
}

/**
 * Tell the client about all component with roles. This code tells the clients about all components right now. 
 */
proto.configClient = function(client, user){

	var init = {
		clientID: client.id,
		user: user
	};
	
	var c = this.componentManager.getAllComponents();
	var componentList = {};
	
	for (var p in c) {
		
		var comp = c[p];
				
		if (comp._roles_) {
			componentList[comp._rmiID_] = {
				classPath: comp._classPath_,
				resourcePath: comp._resourcePath_,
				fullResourcePath: comp._fullResourcePath_,
				roles: comp._roles_
			};
		}
		
	}
	
	//for (var p in componentList) trace("configClient> " + p + ": " + componentList[p]);
	
	init.componentList = componentList;
	
	// Call the client 
	client.call("_$_", null, init); // JUST A PLACE HOLDER FOR NOW! 
	
}

/**
 * authorizationFault results in a call to onRejectedDisconnect.
 * and then the client is rejected.
 * The status is set to rejected. If the component is the last to 
 * report the client is removed from the authorizationClients object.
 */
proto.authorizationFault = function(client, fault){

	
	// If we get here a listener has not authorized a client.
	var authClientRecord = this.authorizationClients.getClientByID(client.id);
	
	if (! authClientRecord){
		trace("ERROR! authorizationResult: Can't get authClientRecord for client id: " + client.id + " Rejecting client connection.");
		this.application.rejectConnection(client, fault);
		return;
	}
		
	if (authClientRecord.status != "rejected"){
		authClientRecord.status = "rejected";
		this.application.onRejectedDisconnect(client, fault);
		this.application.rejectConnection(client, fault);
	}
	
	authClientRecord.listenerReports++;
	
	if (authClientRecord.listenerReports >= authClientRecord.listenerCount){
		this.authorizationClients.removeClientByID(client.id);
	}
	
}


/**
 *
 *
 */
proto.addPendingClient = function(client){
	if (! this.pendingClients) {
		this.pendingClients = new PendingClients(this);
	}
	
	this.pendingClients.addClient(client, arguments); 
}

/**
 *
 *
 */
proto.removePendingClient = function(client){
	if (! this.pendingClients) {
		this.pendingClients = new PendingClients(this);
	}
	
	this.pendingClients.removeClient(client); 
}

/**
 *
 *
 */
proto.processPendingClients = function(){
	var client;
	var args;
	if (! this.pendingClients) return;
	var clients = this.pendingClients.getClients();
	for (var id in clients){
		client = clients[id];
		args = client.arguments;
		delete client.arguments;
		this.application.onConnect.apply(this.application, args);
	}
}


/**
 *
 *
 */
proto.setClientAccessPaths = function(client){
	client.writeAccess = "";
	client.readAccess = "";
	// Get the user for this client
	// Get all the current components
	var components = this.componentManager.getAllComponents();
	var user = client.rtcf.user;
	
	var readArray = [];
	var writeArray = [];
	
	for (var id in components){
		var componentReadAccessPaths  = components[id]._readAccessPaths_;
		var componentWriteAccessPaths = components[id]._writeAccessPaths_;
		
		//trace("Component ID: " + id);
		// for (var rac in componentReadAccessPaths)  trace(rac + ":r: " + componentReadAccessPaths[rac]);
		// for (var rac in componentWriteAccessPaths) trace(rac + ":w: " + componentWriteAccessPaths[rac]);
		
		//trace("Checking component " + components[id]._rmiID_);
		var roleName;
		for (roleName in componentReadAccessPaths){
			//trace("read role: " + roleName);
			if (roleName == "_all_" || user.roles[roleName]){ 
				for (var path in componentReadAccessPaths[roleName]) {
					readArray.push(path);
					//trace("read path: " + path);
				}
			}
		}
		for (roleName in componentWriteAccessPaths){
			//trace("write role: " + roleName);
			if (roleName == "_all_" || user.roles[roleName]) {
				for (var path in componentWriteAccessPaths[roleName]) {
					writeArray.push(path);
					//trace("write path: " + path);
				}
			}
		}
	}
	
	//trace("readArray.length: " + readArray.length);
	//trace("writeArray.length: " + writeArray.length);
	
	client.readAccess = readArray.join(";");
	client.writeAccess = writeArray.join(";");

	//trace("client.readAccess: " + client.readAccess);
	//trace("client.writeAccess: " + client.writeAccess);
} 

//// ==== ---- Wrapping the Real application object ---- ==== ////

/**
 * To broadcast the application.onXXX events, the application object becomes an  
 * event broadcaster. 
 * Once the application object is setup, a reference to it is reserved and then 
 * the application object is overwritten with a proxy application object. 
 */
proto.hijackApplication = function(){
	var i;
	var methodName;
	var methodNames;
	
	// Make the application object into a broadcaster:
	EventBroadcaster.init(application);

	// Hijack the application in a similar way to the Macromedia/Adobe Communication Components.
	
	// Keep a copy of the real application object
	this.application = application;
	
	// Create the application.onXXX methods.
	var rtcf = this;
	
	// This is the method that is called when clients are trying to connect
	// after the application instance is fully initialized.
	this.application.onNormalConnect = function(client, credentialsObject){
	
		// Not every client can pass in a credentials object:
		var credentials = credentialsObject;
		if (typeof credentials != "object"){
			if (application.rtcf.allowNonCredentialedConnections){
				credentials = {
					displayName: "NonCredentialedConnection",
					type:"NonCredentialedConnection"
				}
			}
			else {
				trace("ERROR: credentials object must be the second parameter passed to rtcfConnection.connect(uri, credentials)");
				return false;
			}
		}	
	
		var listenerCount = this.getActiveListenerCount("onConnect");
		// If there are no listeners report the error and return false.
		if (listenerCount <= 0){
			trace("ERROR! There are no listeners for onConnect. Rejecting client.");
			return false;
		}
		
		// Tell the framework we are going to authenticate the client.
		rtcf.addAuthenticatonAttempt(client, listenerCount);
		// Make sure we substitute the crendentials object in case we had to 
		// make one up and put it into the params array to pass on to 
		// all listeners
		var params = [client, credentials];
		for (var i = 2; i < arguments.length; i++){
			params[i] = arguments[i];
		}
		var resultsArray = this.broadcastEventReturnResults.apply(this, this.toArray("onConnect", params));
		// Return null keeps this client pending until we know if we can accept or reject it.
		return null;
	}
	
	this.application.onNormalDisconnect = function(client){
		// If the client was accepted then onAuthorizedDisconnect
		if (client.rtcf) {
			this.broadcastEvent.apply(this, this.toArray("onAuthorizedDisconnect", arguments));
		}
		return this.broadcastEvent.apply(this, this.toArray("onDisconnect", arguments));
	}
	
	this.application.onPendingConnect = function(client){
		return rtcf.addPendingClient.apply(rtcf, arguments);
	}
	
	this.application.onPendingDisconnect = function(client){
		return rtcf.removePendingClient.apply(rtcf, arguments);	
	}
	
	this.application.onConnect = this.application.onNormalConnect;
	
	this.application.onDisconnect = this.application.onNormalDisconnect;
	
	this.application.onAuthenticate = function(client){
		var listenerCount = this.getActiveListenerCount("onAuthenticate");
		// If there are no listeners report the error and return false.
		if (listenerCount <= 0){
			trace("ERROR! There are no listeners for onAuthenticate. Rejecting client.");
			this.rejectConnection(client);
		}
		// Tell the framework we are going to authorize the client.
		rtcf.addAuthorizationAttempt(client, listenerCount);
		var resultsArray = this.broadcastEventReturnResults.apply(this, this.toArray("onAuthenticate", arguments));
		return null;	
	}	
	this.application.onAuthorize = function(){return this.broadcastEvent.apply(this, this.toArray("onAuthorize", arguments));}	
	
	this.application.onAuthorizedConnect = function(){return this.broadcastEvent.apply(this, this.toArray("onAuthorizedConnect", arguments));}
	
	this.application.onAuthorizedDisconnect = function(){return this.broadcastEvent.apply(this, this.toArray("onAuthorizedDisconnect", arguments));}	
	
	this.application.onRejectedDisconnect = function(){return this.broadcastEvent.apply(this, this.toArray("onRejectedDisconnect", arguments));}	
	
	this.application.onAppStart = function(){return this.broadcastEvent.apply(this, this.toArray("onAppStart", arguments));}	
	
	this.application.onAppStop = function(){return this.broadcastEvent.apply(this, this.toArray("onAppStop", arguments));}	
	
	// Look into this one some more:
	this.application.onStatus = function(){ if (this.onStatus) return this.broadcastEvent.apply(this, this.toArray("onStatus", arguments));}	
	this.application.onPublish = function(){return this.broadcastEvent.apply(this, this.toArray("onPublish", arguments));}	
	this.application.onUnpublish = function(){return this.broadcastEvent.apply(this, this.toArray("onUnpublish", arguments));}	


	this.application.toArray = function(methodName, obj){
		var a = [methodName];
		var len = obj.length;
		var i;
		for (i = 0; i < len; i++){
			a[i + 1] = obj[i];
		}
		return a;
	}
	
	// Create an application proxy for developers to customize if they must write code in onXXXXXX methods
	application = new ProxyApplication(this); 
}

//// ==== ---- Wrapper methods so people can call application.rtcf.addListener/remoteListener ---- ==== ////

proto.addListener = function(methodName, object){
	this.application.addListener(methodName, object);
}

proto.removeListener = function(methodName, object){
	this.application.removeListener(methodName, object);
}


//// ==== ---- Proxy application object ---- ==== ////

function ProxyApplication(rtcf){

	this.rtcf = rtcf;
	
	var methodNames = RTCF.EVENT_HANDLER_NAMES;
	
	for (var i = 0; i < methodNames.length; i++) {
		// Clear the onXXX methods from the proxy object so they won't be called 
		// unless the user defines them:
		this[methodNames[i]] = null;
		
		// Ask to listen for each event:
		rtcf.addListener(methodNames[i], this);
	}
	
	// Add back in the onStatus method?
	// this.onStatus = function(){};
	
}

// Make all the application's methods available on the proxy application object.
ProxyApplication.prototype = application; 


//// ==== ---- Pending clients object ---- ==== ////

function PendingClients(rtcf){
	this.rtcf = rtcf;
	this.length = 0;
	this.clients = {};
}

PendingClients.prototype.addClient = function(client, args){
	client.arguments = args;
	this.clients[client.id] = client;
	this.length++;
}

PendingClients.prototype.getClientByID = function(id){
	return this.clients[id];
}

PendingClients.prototype.removeClient = function(client){
	
	if (! this.clients[client.id]){
		trace("ERROR! PendingClients.removeClient> no client at id: " + client.id);
		return;	
	}
	
	delete this.clients[client.id];
	delete client.remainingArguments;
	this.length--;
}

PendingClients.prototype.removeClientByID = function(id){
	
	var client = this.clients[id];
	
	if (! client){
		trace("ERROR! PendingClients.removeClient> no client at id: " + id);
		return;	
	}
	
	delete this.clients[id];
	delete client.remainingArguments;
	this.length--;
}

PendingClients.prototype.getClients = function(){
	return this.clients;
}

//// ==== ---- Component Manager ---- ==== ////

function ComponentManager(rtcf){
	this.rtcf = rtcf;
	this.componentsByID = {};
	this.componentsbyResourcePath = {};
	this.classPaths = {};
	this.length = 0;
}

ComponentManager.prototype.addComponent = function(component, id, classPath, resourcePath){
	this.componentsByID[id] = component;
	this.classPaths[classPath] = true;
	this.componentsbyResourcePath[resourcePath] = component;
}

ComponentManager.prototype.getByID = function(id){
	return this.componentsByID[id];
}


ComponentManager.prototype.getByResourcePath = function(resourcePath){
	return this.componentsbyResourcePath[resourcePath];
}


ComponentManager.prototype.classLoaded = function(classPath){
	return this.classPaths[classPath];
}

ComponentManager.prototype.getAllComponents = function(){
	return this.componentsByID
}

// Finally, create the framework as an object on the proxy application object.
application.rtcf = new RTCF();

} // end catch
